package com.google.code.yargon.ui.component;


import static org.lwjgl.opengl.EXTFramebufferObject.*;
import static org.lwjgl.opengl.GL11.*;

import java.nio.IntBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBVertexBufferObject;

import com.google.code.yargon.ui.RenderEngine;
import com.google.code.yargon.util.YargonUtil;

/**
 * Buffered UI component. Each child class must implement method render() that does all the drawing what the component
 * needs to show on the screen. This base class takes care of buffering this rendered scene to a frame buffer object and displaying on the screen (via ComponentManager).
 * Each child class must also implement onClick(String id) which listens for mouse inputs. ComponentManager will call this method with given mouse hotspot id, defined with 
 * components addHotspot() method. 
 * 
 * @author Mika Myllynen
 *
 */
public abstract class BufferedComponent extends Component {
	
	private int texId;
	private int fbId;
	protected int vboId;
		
	public BufferedComponent(int x, int y, int w, int h, float alpha) {
		super(x, y, w, h, alpha);
		initBuffer();
	}
	
	/**
	 * Initialize buffer (creates frame buffer and vertex buffer objects)
	 * 
	 */
	private void initBuffer() {
		
		// Target texture dimensions must be power of two
		int texW = YargonUtil.nextPow(getWidth());
		int texH = YargonUtil.nextPow(getHeight());
		
		// Generate frame buffer and texture id's
		fbId = glGenFramebuffersEXT();
		texId = glGenTextures();
		
		// Generate vertex buffer object if the render mode requires it
		if(RenderEngine.getRenderMode() == 1) {
			IntBuffer ib = BufferUtils.createIntBuffer(1);
			ARBVertexBufferObject.glGenBuffersARB(ib);
			vboId = ib.get(0);
		}
		
		glPushMatrix();
		RenderEngine.initView(getWidth(), getHeight());				
		
		// Switch to frame buffer and attach texture to it
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbId); 	
		glBindTexture(GL_TEXTURE_2D, texId);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texW, texH, 0, GL_RGBA, GL_INT, (java.nio.ByteBuffer) null);	
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,GL_COLOR_ATTACHMENT0_EXT,GL_TEXTURE_2D, texId, 0);
	}
	
	/**
	 * Binds the frame buffer and clears it
	 * 
	 */
	private void bindBuffer() {
		glPushMatrix();	
		RenderEngine.initView(getWidth(), getHeight());					
		glEnable(GL_TEXTURE_2D);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbId); 
		RenderEngine.clear();
	}
	
	/**
	 * Unbinds the frame buffer
	 * 
	 */
	private void unbindBuffer() {
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
		glDisable(GL_TEXTURE_2D);
		RenderEngine.resetView();		
		glPopMatrix();
	}
	
	/**
	 * Refresh the component (i.e. render to texture using frame buffer object)
	 */
	protected void buffer() {
		bindBuffer();									
		render();								
		unbindBuffer();									
	}
	
	/**
	 * This is where the drawing happens. Each child component must implement this according to their needs.
	 * 
	 */
	protected abstract void render();
		
	/**
	 * Display component on screen at the currently defined location
	 * 
	 */
	protected void display() {
				
		float texW = (float)getWidth() / YargonUtil.nextPow(getWidth()); 
		float texH = (float)getHeight() / YargonUtil.nextPow(getHeight());
		
		glPushMatrix();
		glLoadIdentity();
		
		glEnable(GL_ALPHA_TEST);
		glAlphaFunc(GL_GREATER, 0.0f);
		
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, texId);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 
			
		if(getAlpha() < 1.0f) {
			glEnable(GL_BLEND);
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			glColor4f(1.0f, 1.0f, 1.0f, getAlpha()); 
		} 
		
		glBegin(GL_QUADS);
		{
			glTexCoord2f(0, 0);
			glVertex2f(getX(), getY());		
			
			glTexCoord2f(texW, 0);
			glVertex2f(getX() + getWidth(), getY());
			
			glTexCoord2f(texW, texH);
			glVertex2f(getX() + getWidth(), getY() + getHeight());
			
			glTexCoord2f(0, texH);
			glVertex2f(getX(),  getY() + getHeight());			
		}
		glEnd();	
		
		if(getAlpha() < 1.0f) {
			glDisable(GL_BLEND);
		} 
	
		glDisable(GL_TEXTURE_2D);
		glDisable(GL_ALPHA_TEST);
		glPopMatrix();
	}
	
	/**
	 * This reacts to mouse clicks. Each child component must implement this according to their needs.
	 * 
	 * @param id
	 */
	protected abstract void onClick(String id);
}
